package com.yhb.service.impl;

import com.yhb.conf.ResultCode;
import com.yhb.model.Card;
import com.yhb.model.ChannelFlow;
import com.yhb.model.Plan;
import com.yhb.model.User;
import com.yhb.service.CardService;
import com.yhb.service.ChannelFlowService;
import com.yhb.utils.DateUtils;
import me.edzh.spring.bootstrap.utils.MathUtils;
import me.edzh.spring.bootstrap.utils.StringUtils;
import me.edzh.spring.bootstrap.utils.exception.ServiceException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;

@Service
public class ChannelFlowServiceImpl implements ChannelFlowService {

    @Autowired
    private CardService cardService;

    // TODO 回包结构改成Plan  因为需要给到手续费等值
    @Override
    public Plan aiGenerateFlows(User user, String cardId, BigDecimal totalPayAmount, BigDecimal remainingAmount) {
        Card card = cardService.cardWithOwnerCheck(cardId, user.getId());

        if (StringUtils.isEmpty(card.getPayDay())) throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "未设置还款日");

        Calendar planEnd = DateUtils.getFastestDay(card.getPayDay());
        planEnd.add(Calendar.DAY_OF_MONTH, -3); // 还款日前3天为最后期限

        int days = DateUtils.daysBetweenToday(planEnd);
        int flowCount = days * 3; // 每天3笔

        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        // 每天3笔消费3笔还款 分布在 每天的 9:42-10:18, 11:42-12:18,
        //                              13:42-14:18, 15:42-16:18,
        //                              17:42-18:18, 19:42-20:18,

        int startHourIdx = 3;


        if (hour < 9) {
            startHourIdx = 0;
            flowCount += 3;
            days++;
        } else if (hour < 13) {
            startHourIdx = 1;
            flowCount += 2;
            days++;
        } else if (hour < 17) {
            startHourIdx = 2;
            flowCount += 1;
            days++;
        }


        // 把总额加上手续费
        BigDecimal totalValue = calculateAmountWithinCommission(user, card, totalPayAmount);

        // 把总额加上提现费
        totalValue = totalValue.add(BigDecimal.valueOf(flowCount * calculateRemitFee(user, totalValue, RemitType.SCHEDULED).doubleValue()));

        // TODO 单笔均值过小需要减少每天笔数, 单日均值过小的需要隔天还款, 以上措施均用以保证单笔数额 需要动到flowCountPerDay, average等参数

        BigDecimal average = totalValue.divide(BigDecimal.valueOf(flowCount), 0, RoundingMode.CEILING);





        List<ChannelFlow> flows = new ArrayList<>(flowCount);
        for (int i = 0; i < flowCount; ++i) {

            flows.add(ChannelFlow.builder()
                    .type(ChannelFlow.Type.CONSUME)
                    .status(ChannelFlow.Status.PENDING)
                    .cardId(card.getId())
                    .build());

        }

        // 随机抖动金额
        fillFlowAmount(flows, average);

        List<ChannelFlow> result = new ArrayList<>(flowCount * 2);
        for(ChannelFlow flow : flows) {
            result.add(flow);
            BigDecimal consumeAmount = flow.getAmount();
            BigDecimal commission = calculateCommission(user, card, consumeAmount);
            BigDecimal remitFee = calculateRemitFee(user, consumeAmount, RemitType.SCHEDULED);
            flow.setCommission(commission);

            ChannelFlow payFlow = ChannelFlow.builder()
                    .type(ChannelFlow.Type.REMIT)
                    .cardId(card.getId())
                    .amount(consumeAmount.subtract(commission).subtract(remitFee))
                    .status(ChannelFlow.Status.PENDING)
                    .remitFee(remitFee)
                    .build();

            result.add(payFlow);

        }

        fillFlowDate(result,startHourIdx, 3);

        return Plan.builder()
                .days((long)days)
                .remaining(BigDecimal.ZERO)
                .flows(result)
                .endDate(planEnd.getTime())
                .build();
    }

    @Override
    public BigDecimal calculateCommission(User user, Card card, BigDecimal amount) {
        return amount.multiply(BigDecimal.ONE.subtract(getCommissionRate(user, card)));
    }

    @Override
    public BigDecimal calculateAmountWithinCommission(User user, Card card, BigDecimal amount) {
        return amount.divide(getCommissionRate(user, card), 3, RoundingMode.HALF_EVEN);
    }

    @Override
    public BigDecimal calculateRemitFee(User user, BigDecimal amount, RemitType type) {
        if (type == RemitType.RAPID) return BigDecimal.ONE;
        else if (type == RemitType.SCHEDULED) return BigDecimal.ONE;

        throw new ServiceException(ResultCode.ERROR_PARAMETER_WRONG, "have not implemented all remit types");
    }

    @Override
    public String getPosCommission(User.VipType vipType) {
        switch (vipType) {
            case PAYED:
                return "0.0058";
            case PRIME:
                return "0.0054";
            case ORDINARY:
            default:
                return "0.0068";
        }

    }

    @Override
    public BigDecimal getCommissionRate(User user, Card consumeCard) {
        if (extraCommissionBanks.contains(consumeCard.getBankName())) {
            switch (user.getVipType()) {
                case PAYED:
                    return BigDecimal.valueOf(0.9935); //0.65%
                case PRIME:
                    return BigDecimal.valueOf(0.9943); //0.57%
                default:
                case ORDINARY:
                    return BigDecimal.valueOf(0.9925); //0.75%
            }
        }


        switch (user.getVipType()) {
            case PAYED:
                return BigDecimal.valueOf(0.9945); //0.55%
            case PRIME:
                return BigDecimal.valueOf(0.9953); //0.47%
            default:
            case ORDINARY:
                return BigDecimal.valueOf(0.9932); //0.68%
        }
    }
    private final static List<String> extraCommissionBanks = new ArrayList<String> () {{
        add("邮政银行");
        add("中国邮政储蓄银行");
        add("中国邮政银行");
        add("招商银行");
        add("中国招商银行");
        add("中国农业银行");
        add("农业银行");
        add("交通银行");
        add("中国交通银行");
        add("民生银行");
        add("中国民生银行");
        add("光大银行");
        add("中国光大银行");
        add("浦发银行");
        add("上海浦东发展银行");
        add("兴业银行");
        add("中国兴业银行");
    }};


    /**
     * @param startHourIdx 从第几个组合开始, 如 2, 则从第三个组合开始(hours[4] 消费, hours[5]还款)
     * @param flowCountPerDay 每天笔数  最大4笔
     */
    private static void fillFlowDate(List<ChannelFlow> flows, int startHourIdx, int flowCountPerDay) {
        int[] hours;
        int[] minuteOffsets = new int[]{0, 30}; // 仅flowCountPerDay == 4 时用到
        switch (flowCountPerDay) {
            case 2:
                hours = new int[]{10, 13, 16, 19};
                break;
            case 3:
                hours = new int[]{10, 12, 14, 16, 18, 20};
                break;
            case 4:
                hours = new int[]{10, 11, 13, 14, 16, 17, 19, 20};
                break;
            default:
            case 1:
                hours = new int[]{10, 18};
                break;
        }

        final int MINUTE_RANDOM_RANGE = 18;
        Calendar calendar = Calendar.getInstance();
        int hourIdx = startHourIdx * 2 - 1;
        for (ChannelFlow channelFlow : flows) {
            hourIdx++;
            if (hourIdx >= hours.length) {
                calendar.add(Calendar.DAY_OF_MONTH, 1);
                hourIdx = 0;
            }

            // 随机抖动时间
            int m = MathUtils.random(-MINUTE_RANDOM_RANGE, MINUTE_RANDOM_RANGE);
            int s = MathUtils.random(0, 59);

            calendar.set(Calendar.HOUR_OF_DAY, hours[hourIdx]);
            calendar.set(Calendar.MINUTE, flowCountPerDay == 4 ? minuteOffsets[hourIdx % 2] : 0);
            calendar.add(Calendar.MINUTE, m);
            calendar.set(Calendar.SECOND, s);

            channelFlow.setExecutionTime(calendar.getTime());
        }
    }

    /**
     * 将金额随机填入flow
     *
     * @param flows   已经填好时间的flows
     * @param average 平均每笔金额
     */
    private static void fillFlowAmount(List<ChannelFlow> flows, BigDecimal average) {


        int randomRange = (int)Math.min(average.doubleValue() - 100, 1000 - average.doubleValue()); // 单笔均值离端点的距离为扰动值

        if (average.subtract(BigDecimal.valueOf(900)).compareTo(BigDecimal.ZERO) > 0) { // 大于900直接抛异常
            throw new ServiceException(ResultCode.ERROR_FLOW_AVERAGE_OUT_OF_RANGE, "单笔金额过大");
        }

        if (average.subtract(BigDecimal.valueOf(112)).compareTo(BigDecimal.ZERO) < 0) { // 小于112(随机化后每笔最小能到100) 直接抛异常
            throw new ServiceException(ResultCode.ERROR_FLOW_AVERAGE_OUT_OF_RANGE, "单笔金额过小");
        }

        if (flows.size() == 1) {
            flows.get(0).setAmount(average);
            return;
        }

        int count = flows.size();
        if (flows.size() % 2 == 1) { // 奇数条flow需要取3条出来单独运算
            ChannelFlow lastFlow = flows.get(count - 1);
            ChannelFlow lastFlow1 = flows.get(count - 2);
            ChannelFlow lastFlow2 = flows.get(count - 3);

            int r = MathUtils.random(-randomRange, randomRange, true); // 把倒数第一个 - 随机值 倒数第二个第三个分别加随机值的 1/3, 2/3
            lastFlow.setAmount(average.subtract(BigDecimal.valueOf(r)));
            lastFlow1.setAmount(average.add(BigDecimal.valueOf(r / 3)));
            lastFlow2.setAmount(average.add(BigDecimal.valueOf(r / 3 * 2)));

            count -= 3;
        }

        for (int i = 0; i < count / 2; ++i) {
            ChannelFlow flow = flows.get(i);
            ChannelFlow oppositeFlow = flows.get(count - i - 1);
            BigDecimal r = BigDecimal.valueOf(MathUtils.random(-randomRange, randomRange, true));
            flow.setAmount(average.subtract(r));
            oppositeFlow.setAmount(average.add(r));
        }
    }
}
